import functools
from types import FunctionType

from django.db.models import ForeignKey, ManyToManyField
from django.forms import ModelForm, Form
from django.http import QueryDict
from django.shortcuts import render, redirect
from django.urls import re_path
from django.urls import reverse
from django.utils.safestring import mark_safe

from stark.utils.pagination import Pagination


def get_choice_text(text, field):
    """
    获取choice字段的文本
    :param text: choice字段的名称
    :param field: choice字段
    :return:
    """

    def inner(self, obj=None, is_header=None, *args, **kwargs):
        if is_header:
            return text
        method = 'get_%s_display' % field
        return getattr(obj, method)()

    return inner


def get_boolean_text(text, field):
    """
    获取boolean字段的文本
    :param text: choice字段的名称
    :param field: choice字段
    :return:
    """

    def inner(self, obj=None, is_header=None, *args, **kwargs):
        if is_header:
            return text
        return '是' if getattr(obj, field) else '否'

    return inner


def get_datetime_text(text, field, date_formate='%Y-%m-%d'):
    """
    获取choice字段的文本
    :param text: choice字段的名称
    :param field: choice字段
    :return:
    """

    def inner(self, obj=None, is_header=None, *args, **kwargs):
        if is_header:
            return text
        datetime = getattr(obj, field)
        return datetime.strftime(date_formate)

    return inner


def get_m2m_text(text, field):
    """
    获取choice字段的文本
    :param text: choice字段的名称
    :param field: choice字段
    :return:
    """

    def inner(self, obj=None, is_header=None, *args, **kwargs):
        if is_header:
            return text
        queryset = getattr(obj, field).all()
        text_list = [str(row) for row in queryset]
        return ','.join(text_list)

    return inner


class SearchGroupRow(object):
    def __init__(self, title, queryset_or_tuple, option, field_object, query_dict):
        self.queryset_or_tuple = queryset_or_tuple
        self.option = option
        self.field_object = field_object
        self.query_dict = query_dict
        self.title = title

    def __iter__(self):

        yield '<div class="whole">'
        yield self.title
        yield '</div>'
        yield '<div class="others">'
        total_query_dict = self.query_dict.copy()
        total_query_dict._mutable = True

        origin_value_list = self.query_dict.getlist(self.option.field)
        if not origin_value_list:
            yield '<a class="active" href="?%s">全部</a>' % total_query_dict.urlencode()
        else:
            total_query_dict.pop(self.option.field)
            yield '<a href="?%s">全部</a>' % total_query_dict.urlencode()
        for item in self.queryset_or_tuple:
            text = self.option.get_text(item)
            value = str(self.option.get_value(item))
            query_dict = self.query_dict.copy()
            query_dict._mutable = True
            if not self.option.is_multi:
                query_dict[self.option.field] = value
                if value in origin_value_list:
                    query_dict.pop(self.option.field)
                    yield '<a class="active" href="?%s">%s</a>' % (query_dict.urlencode(), text)
                else:
                    yield '<a href="?%s">%s</a>' % (query_dict.urlencode(), text)
            else:
                multi_value_list = self.query_dict.getlist(self.option.field)
                if value in multi_value_list:
                    multi_value_list.remove(value)
                    query_dict.setlist(self.option.field, multi_value_list)
                    yield '<a class="active" href="?%s">%s</a>' % (query_dict.urlencode(), text)
                else:
                    multi_value_list.append(value)
                    query_dict.setlist(self.option.field, multi_value_list)
                    yield '<a href="?%s">%s</a>' % (query_dict.urlencode(), text)
        yield '</div>'


class Option(object):
    def __init__(self, field, is_multi=False, db_condition=None, text_func=None, value_func=None):
        """
        :param field:组合搜索关联的字段
        :param db_condition: 数据库关联查询的条件
        :param is_multi: 数据库关联查询的条件
        :param text_func: 此函数用于组合搜索查询条件的文本
        :param value_func: 此函数用于组合搜索查询按钮
        """
        self.field = field
        self.is_multi = is_multi
        if not db_condition:
            db_condition = {}
        self.db_condition = db_condition
        self.text_func = text_func
        self.value_func = value_func
        self.choice = False

    def get_db_condition(self):
        return self.db_condition

    def get_queryset_or_tuple(self, request, model_class, *args, **kwargs):
        # 判断获取到的model类的字段对象类型：FK或者M2M
        field_object = model_class._meta.get_field(self.field)
        title = field_object.verbose_name
        if isinstance(field_object, ForeignKey) or isinstance(field_object, ManyToManyField):
            db_condition = self.get_db_condition()
            return SearchGroupRow(title, field_object.related_model.objects.filter(**db_condition).order_by('id'),
                                  self, field_object, request.GET)
        else:
            self.choice = True
            return SearchGroupRow(title, field_object.choices, self, field_object, request.GET)

    def get_text(self, field_object):

        if self.text_func:
            return self.text_func(field_object)
        if self.choice:
            return field_object[1]
        return str(field_object)

    def get_value(self, field_object):

        if self.value_func:
            return self.value_func(field_object)
        if self.choice:
            return field_object[0]
        return field_object.pk


class StarkModelForm(ModelForm):
    exclude_bootstrap_class = []

    def __init__(self, *args, **kwargs):
        super(StarkModelForm, self).__init__(*args, **kwargs)
        for name, field in self.fields.items():
            if name in self.exclude_bootstrap_class:
                continue
            field.widget.attrs['class'] = 'form-control'


class StarkForm(Form):
    exclude_bootstrap_class = []

    def __init__(self, *args, **kwargs):
        super(StarkForm, self).__init__(*args, **kwargs)
        for name, field in self.fields.items():
            if name in self.exclude_bootstrap_class:
                continue
            field.widget.attrs['class'] = 'form-control'


class StarkHandler(object):
    list_template = None
    add_template = None
    change_template = None
    delete_template = None
    list_display = []
    per_page = 10

    def __init__(self, model_class, prev, site):
        self.model_class = model_class
        self.prev = prev
        self.site = site
        self.request = None

    def get_queryset(self, request, *args, **kwargs):
        return self.model_class.objects

    def list_view(self, request, *args, **kwargs):
        """
        列表页面
        :param request:
        :return:
        """
        # ##################1.批量操作 ###############
        action_list = self.get_action_list()
        action_dict = {func.__name__: func.text for func in action_list}

        if request.method == 'POST':
            action_func_name = request.POST.get('action')
            if action_func_name and action_func_name in action_dict:
                func_response = getattr(self, action_func_name)(request, *args, **kwargs)
                if func_response:
                    return func_response
        # 获取搜索条件

        search_list = self.get_search_list()
        """
        1.如果search_list没有值则不显示搜索框
        2.获取用户提交的关键字
        """
        search_value = request.GET.get('q', '')
        from django.db.models import Q
        """
        Q用于构造复杂的ORM查询条件
        """
        conn = Q()
        conn.connector = 'OR'
        if search_value:
            for item in search_list:
                conn.children.append((item, search_value))
        # 1.获取排序
        order_list = self.get_order_list()
        search_group_condition = self.get_search_group_condition(request)
        prev_queryset = self.get_queryset(request, *args, **kwargs)

        queryset = prev_queryset.filter(conn).filter(**search_group_condition).order_by(*order_list)
        # 处理分页
        all_count = queryset.count()
        query_params = request.GET.copy()
        query_params._mutable = True
        pager = Pagination(
            current_page=request.GET.get('page'),
            all_count=all_count,
            base_url=request.path_info,
            query_params=query_params,
            per_page=self.per_page
        )
        # 1.处理表头
        header_list = []
        list_display = self.get_list_display(request, *args, **kwargs)
        if list_display:
            for key_or_func in list_display:
                if isinstance(key_or_func, FunctionType):
                    header_list.append(key_or_func(self, None, True))
                else:
                    verbose_name = self.model_class._meta.get_field(key_or_func).verbose_name
                    header_list.append(verbose_name)
        else:
            header_list.append(self.model_class._meta.model_name)
        # 2.处理表格内容
        data_list = queryset[pager.start:pager.end]
        body_list = []
        for row in data_list:
            tr_list = []
            if list_display:
                for key_or_func in list_display:
                    if isinstance(key_or_func, FunctionType):
                        tr_list.append(key_or_func(self, row, False, *args, **kwargs))
                    else:
                        tr_list.append('' if getattr(row, key_or_func) == None else getattr(row, key_or_func))
            else:
                tr_list.append(row)
            body_list.append(tr_list)
        # 按钮添加
        add_btn = self.get_add_btn(request, *args, **kwargs)
        # 组合搜索
        search_group = self.get_search_group(request)
        search_group_list = []
        for option_object in search_group:
            search_group_list.append(option_object.get_queryset_or_tuple(request, self.model_class))
        return render(
            request,
            self.list_template or 'stark/list.html',
            {
                'body_list': body_list,
                'header_list': header_list,
                'pager': pager,
                'add_btn': add_btn,
                'search_list': search_list,
                'search_value': search_value,
                'action_dict': action_dict,
                'search_group_row_list': search_group_list
            }
        )

    def add_view(self, request, *args, **kwargs):
        """
        添加页面
        :return:
        """
        model_form_class = self.get_model_class_form(True, request, None, *args, **kwargs)
        if request.method == 'GET':
            form = model_form_class()
            return render(request, self.add_template or 'stark/change.html', {'form': form})
        form = model_form_class(data=request.POST)
        if form.is_valid():
            response = self.save(request, form, False, *args, **kwargs)
            return response or redirect(self.reverse_list_url(*args, **kwargs))
        return render(request, self.add_template or 'stark/change.html', {'form': form})

    def get_change_object(self, request, pk, *args, **kwargs):
        return self.model_class.objects.filter(id=pk).first()

    def change_view(self, request, pk, *args, **kwargs):
        """
        编辑页面
        :param request:
        :param pk:
        :return:
        """
        model_form_class = self.get_model_class_form(False, request, pk, *args, **kwargs)
        current_change_obj = self.get_change_object(request, pk, *args, **kwargs)
        if not current_change_obj:
            info = '数据不存在，请重新选择!'
            return render(request, 'stark/hint.html', {'msg': info})
        if request.method == "GET":
            form = model_form_class(instance=current_change_obj)
            return render(request, self.change_template or 'stark/change.html', {'form': form})
        form = model_form_class(instance=current_change_obj, data=request.POST)
        if form.is_valid():
            response = self.save(request, form, is_update=True, *args, **kwargs)
            return response or redirect(self.reverse_list_url(*args, **kwargs))
        return render(request, self.change_template or 'stark/change.html', {'form': form})

    def get_delete_object(self, request, pk, *args, **kwargs):
        return self.model_class.objects.filter(id=pk).first()

    def delete_view(self, request, pk, *args, **kwargs):
        """
        删除页面
        :param request:
        :return:
        """
        origin_url = self.reverse_list_url(*args, **kwargs)
        current_delete_obj = self.get_delete_object(request, pk, *args, **kwargs)
        if not current_delete_obj:
            info = '数据不存在，请重新选择!'
            return render(request, 'stark/hint.html', {'msg': info})
        if request.method == 'GET':
            return render(request, self.delete_template or 'stark/delete.html', {'cancel_url': origin_url})
        current_delete_obj.delete()
        return redirect(origin_url)

    def get_url_name(self, param):
        """
        返回URL别名
        :param param: 列表，添加，编辑，删除
        :return:
        """
        app_name, model_name = self.model_class._meta.app_label, self.model_class._meta.model_name
        if self.prev:
            return '%s_%s_%s_%s' % (app_name, model_name, self.prev, param)
        return '%s_%s_%s' % (app_name, model_name, param)

    @property
    def get_list_url_name(self):
        """
        获取列表页面url别名
        :return:
        """
        return self.get_url_name('list')

    @property
    def get_add_url_name(self):
        """
        获取添加页面url别名
        :return:
        """
        return self.get_url_name('add')

    @property
    def get_change_url_name(self):
        """
        获取编辑页面url别名
        :return:
        """
        return self.get_url_name('change')

    @property
    def get_delete_url_name(self):
        """
        获取删除页面url别名
        :return:
        """
        return self.get_url_name('delete')

    def get_urls(self):
        """
        二次路由分发
        :return:
        """
        patterns = [
            re_path(r'^list/$', self.wrapper(self.list_view), name=self.get_list_url_name),
            re_path(r'^add/$', self.wrapper(self.add_view), name=self.get_add_url_name),
            re_path(r'^change/(?P<pk>\d+)/$', self.wrapper(self.change_view),
                    name=self.get_change_url_name),
            re_path(r'^delete/(?P<pk>\d+)/$', self.wrapper(self.delete_view),
                    name=self.get_delete_url_name)
        ]
        patterns.extend(self.extra_url())
        return patterns

    def extra_url(self):
        """
        增加路由
        :return:
        """
        return []

    def get_list_display(self, request, *args, **kwargs):
        """
        预留列表页面显示字段的钩子函数
        :return:
        """
        value = []
        if self.list_display:
            value.extend(self.list_display)
            value.append(type(self).display_edit_and_delete)
        return value

    def display_edit(self, obj=None, is_header=None, *args, **kwargs):
        """
        显示编辑按钮
        :return:
        """
        if is_header:
            return '编辑'
        return mark_safe("<a href='%s'>编辑</a>" % self.reverse_change_url(pk=obj.id))

    def display_delete(self, obj=None, is_header=None, *args, **kwargs):
        """
        显示删除按钮
        :return:
        """
        if is_header:
            return '删除'
        return mark_safe("<a href='%s'>删除</a>" % self.reverse_delete_url(pk=obj.id, *args, **kwargs))

    def display_edit_and_delete(self, obj=None, is_header=None, *args, **kwargs):
        """
        同时显示添加删除按钮
        :param obj:
        :param is_header:
        :return:
        """
        if is_header:
            return '操作'
        tpl = "<a href='%s'>编辑</a> | <a href='%s'>删除</a>" % (
            self.reverse_change_url(pk=obj.id), self.reverse_delete_url(pk=obj.id))
        return mark_safe(tpl)

    def display_checkbox(self, obj=None, is_header=None, *args, **kwargs):
        if is_header:
            return mark_safe('%s选择' % '<input class="check-all" type="checkbox">')
        return mark_safe('<input name=pk type="checkbox" value="%s"/>' % obj.id)

    has_add_btn = True

    def get_add_btn(self, request, *args, **kwargs):
        """
        显示添加按钮
        :return:
        """
        if self.has_add_btn:
            return '<a class="btn btn-primary" href="%s">' \
                   '<i class="fa fa-plus-square" aria-hidden="true"></i>添加</a>' % self.reverse_add_url(*args, **kwargs)
        return None

    def reverse_commons_url(self, name, *args, **kwargs):
        name = '%s:%s' % (self.site.namespace, name)
        base_url = reverse(name, args=args, kwargs=kwargs)
        if not self.request.GET:
            url = base_url
        else:
            param = self.request.GET.urlencode()
            new_queryset_dict = QueryDict(mutable=True)
            new_queryset_dict['_filter'] = param
            url = '%s?%s' % (base_url, new_queryset_dict.urlencode())
        return url

    def reverse_list_url(self, *args, **kwargs):
        """
        列表页面：反向生成带原搜索条件的url
        :return:
        """
        name = "%s:%s" % (self.site.namespace, self.get_list_url_name)
        base_url = reverse(name, args=args, kwargs=kwargs)
        param = self.request.GET.get('_filter')
        if param:
            return "%s?%s" % (base_url, param)
        return base_url

    def reverse_change_url(self, *args, **kwargs):
        return self.reverse_commons_url(self.get_change_url_name, *args, **kwargs)

    def reverse_delete_url(self, *args, **kwargs):
        return self.reverse_commons_url(self.get_delete_url_name, *args, **kwargs)

    def reverse_add_url(self, *args, **kwargs):
        """
        添加页面：反向生成带原搜索条件的url
        :return:
        """
        return self.reverse_commons_url(self.get_add_url_name, *args, **kwargs)

    order_list = []

    def get_order_list(self):
        return self.order_list or ['id', ]

    search_list = []

    def get_search_list(self):
        return self.search_list

    action_list = []

    def get_action_list(self):
        return self.action_list

    def action_multi_delete(self, request, *args, **kwargs):
        pk_list = request.POST.getlist('pk')
        self.model_class.objects.filter(pk__in=pk_list).delete()

    action_multi_delete.text = '批量删除'

    # 组合搜索
    search_group = []

    def get_search_group(self, request):
        return self.search_group

    # 获取组合搜索查询条件
    def get_search_group_condition(self, request):
        """
        获取组合查询条件
        :param request:
        :return:
        """
        condition = {}
        for option in self.get_search_group(request):
            if option.is_multi:
                value_list = request.GET.getlist(option.field)
                if not value_list:
                    continue
                condition["%s__in" % option.field] = value_list
            else:
                value = request.GET.get(option.field)
                if not value:
                    continue
                condition[option.field] = value
        return condition

    def wrapper(self, func):
        @functools.wraps(func)
        def inner(request, *args, **kwargs):
            self.request = request
            return func(request, *args, **kwargs)

        return inner

    def save(self, request, form, is_update=False, *args, **kwargs):
        """
        自定义保存函数
        :param request:
        :param form: 表单
        :param is_update: 判断是添加还是更新
        :return:
        """
        form.save()

    # 预留自定义model_form
    model_form_class = None

    def get_model_class_form(self, is_add, request, pk, *args, **kwargs):
        """
        自定义获取ModelForm类的方法
        :param is_add:
        :return:
        """
        if self.model_form_class:
            return self.model_form_class

        class DynamicModeForm(StarkModelForm):
            class Meta:
                model = self.model_class
                fields = '__all__'

        form = DynamicModeForm
        return form


class StarkSite(object):
    def __init__(self):
        self._registry = []
        self.app_name = 'stark'
        self.namespace = 'stark'

    def register(self, model_class, handler=None, prev=None):
        if not handler:
            handler = StarkHandler
        self._registry.append(
            {'model_class': model_class, 'handler': handler(model_class, prev, self), 'prev': prev})

    def get_urls(self):
        patterns = []
        for item in self._registry:
            model_class = item['model_class']
            handler = item['handler']
            prev = item['prev']
            app_name, model_name = model_class._meta.app_label, model_class._meta.model_name
            if prev:
                patterns.extend([
                    re_path(r'^%s/%s/%s/' % (app_name, model_name, prev), (handler.get_urls(), None, None)),
                ])
            else:
                patterns.extend([
                    re_path(r'^%s/%s/' % (app_name, model_name), (handler.get_urls(), None, None)),
                ])
        return patterns

    @property
    def urls(self):
        return self.get_urls(), self.app_name, self.namespace


site = StarkSite()
